package com.qiangesoft.rdp.starter.minio.core;

import com.qiangesoft.rdp.starter.minio.bucket.BucketAccessPolicyEnum;
import com.qiangesoft.rdp.starter.minio.bucket.BucketPolicyFactory;
import com.qiangesoft.rdp.starter.minio.bucket.policy.BucketPolicyInterface;
import com.qiangesoft.rdp.starter.minio.config.MinioProperties;
import io.minio.*;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;

/**
 * minio文件服务实现类
 *
 * @author qiangesoft
 * @date 2023-04-21
 */
@Slf4j
public class MinioTemplateImpl implements MinioTemplate {

    private static final String SLASH = "/";
    private static final String UNDERLINE = "-";
    private static final String POINT = ".";

    private MinioProperties minioProperties;

    private MinioClient minioClient;

    public MinioTemplateImpl(MinioProperties minioProperties, MinioClient minioClient) {
        this.minioProperties = minioProperties;
        this.minioClient = minioClient;
    }

    @Override
    public String getPath() {
        StoreModeEnum storeMode = minioProperties.getStore();

        LocalDateTime localDateTime = LocalDateTime.now();
        StringBuilder stringBuilder = new StringBuilder();
        switch (storeMode) {
            case YEAR:
                stringBuilder.append(localDateTime.getYear()).append(SLASH);
                break;
            case MONTH:
                stringBuilder.append(localDateTime.getYear()).append(SLASH)
                        .append(localDateTime.getMonthValue()).append(SLASH);
                break;
            case DAY:
                stringBuilder.append(localDateTime.getYear()).append(SLASH)
                        .append(localDateTime.getMonthValue()).append(SLASH)
                        .append(localDateTime.getDayOfMonth()).append(SLASH);
                break;
            case HOUR:
                stringBuilder.append(localDateTime.getYear()).append(SLASH)
                        .append(localDateTime.getMonthValue()).append(SLASH)
                        .append(localDateTime.getDayOfMonth()).append(SLASH)
                        .append(localDateTime.getHour()).append(SLASH);
                break;
            case MINUTE:
                stringBuilder.append(localDateTime.getYear()).append(SLASH)
                        .append(localDateTime.getMonthValue()).append(SLASH)
                        .append(localDateTime.getDayOfMonth()).append(SLASH)
                        .append(localDateTime.getHour()).append(SLASH)
                        .append(localDateTime.getMinute()).append(SLASH);
                break;
            case SECOND:
                stringBuilder.append(localDateTime.getYear()).append(SLASH)
                        .append(localDateTime.getMonthValue()).append(SLASH)
                        .append(localDateTime.getDayOfMonth()).append(SLASH)
                        .append(localDateTime.getHour()).append(SLASH)
                        .append(localDateTime.getMinute()).append(SLASH)
                        .append(localDateTime.getSecond()).append(SLASH);
                break;
        }
        return stringBuilder.deleteCharAt(stringBuilder.length() - 1).toString();
    }

    @Override
    public void createBucket(String bucketName) throws Exception {
        this.createBucket(bucketName, minioProperties.getAccess());
    }

    @Override
    public void createBucket(String bucketName, BucketAccessPolicyEnum bucketPolicyEnum) throws Exception {
        BucketExistsArgs args = BucketExistsArgs.builder().bucket(bucketName).build();
        boolean exists = minioClient.bucketExists(args);
        if (!exists) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());

            // 设置桶策略
            BucketPolicyInterface bucketPolicy = BucketPolicyFactory.getBucketPolicyInterface(bucketPolicyEnum);
            bucketPolicy.createBucketPolicy(minioClient, bucketName);
        }
    }

    @Override
    public List<Bucket> listBuckets() throws Exception {
        return minioClient.listBuckets();
    }

    @Override
    public Bucket getBucket(String bucketName) throws Exception {
        Optional<Bucket> first = this.listBuckets().stream().filter(e -> e.name().equals(bucketName)).findFirst();
        return first.orElse(null);
    }

    @Override
    public void removeBucket(String bucketName) throws Exception {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    @Override
    public String putObject(String bucketName, InputStream inputStream, String fileName) throws Exception {
        // 参数校验
        Assert.notNull(bucketName, "bucketName is not blank");
        Assert.notNull(inputStream, "inputStream is not null");
        Assert.notNull(fileName, "fileName is not blank");

        // 判断存储桶是否存在，不存在则创建
        this.createBucket(bucketName, minioProperties.getAccess());

        // 文件名
        String filePath = this.getPath();
        String objectName = filePath + SLASH + fileName;

        // 上传
        minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName)
                        .stream(inputStream, inputStream.available(), -1)
                        .contentType("application/octet-stream")
                        .build());
        return bucketName + SLASH + objectName;
    }

    @Override
    public String putObject(String bucketName, MultipartFile file) throws Exception {
        // 参数校验
        Assert.notNull(bucketName, "bucketName is not blank");
        Assert.notNull(file, "file is not null");

        // 判断存储桶是否存在  不存在则创建
        createBucket(bucketName, minioProperties.getAccess());

        // 文件名
        String originalFilename = file.getOriginalFilename();
        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(POINT) + 1);
        String filePath = this.getPath();
        String fileName = this.parseFileName(fileExtension);
        String objectName = filePath + SLASH + fileName;

        // 上传
        minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName)
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build());
        return bucketName + SLASH + objectName;
    }

    @Override
    public String getObjectURL(String bucketName, String objectName, Integer expires) throws Exception {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).expiry(expires).build());
    }

    @Override
    public InputStream getObject(String bucketName, String objectName) throws Exception {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    @Override
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    @Override
    public void download(String bucketName, String filename, HttpServletResponse response) throws Exception {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(filename).build();
        GetObjectResponse objectResponse = minioClient.getObject(objectArgs);
        byte[] buf = new byte[1024];
        int len;
        try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
            while ((len = objectResponse.read(buf)) != -1) {
                os.write(buf, 0, len);
            }
            os.flush();
            byte[] bytes = os.toByteArray();
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/force-download");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.addHeader("Content-Disposition", "attachment;fileName=" + filename);
            try (ServletOutputStream stream = response.getOutputStream()) {
                stream.write(bytes);
                stream.flush();
            }
        }
    }

    @Override
    public void removeObject(String bucketName, String objectName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    @Override
    public void removeObjects(String bucketName, List<String> objectNames) throws Exception {
        List<DeleteObject> deleteObjectList = new ArrayList<>();
        for (String objectName : objectNames) {
            DeleteObject deleteObject = new DeleteObject(objectName);
            deleteObjectList.add(deleteObject);
        }
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(deleteObjectList).build());
        for (Result<DeleteError> result : results) {
            DeleteError error = result.get();
            log.error("Error in deleting object " + error.objectName() + "; " + error.message());
        }
    }

    /**
     * 构建文件名
     *
     * @param fileExtension
     */
    private String parseFileName(String fileExtension) {
        return System.currentTimeMillis() + UNDERLINE + new Random().nextInt(1000) + POINT + fileExtension;
    }
}